Debuggers are software tools that can be used to diagnose computer programs and trace errors that arise during execution of the program. Debuggers are commonly used during the development of computer programs, to verify that a program under development operates as intended.
Conventional debuggers typically support two primary operations to assist a computer programmer in debugging program code. A first operation supported by conventional debuggers is a “step” function, which permits a computer programmer to process instructions (also known as “statements”) in a computer program one-by-one, and see the results upon completion of each instruction. While the step operation provides a programmer with a large amount of information about a program during its execution, stepping through hundreds or thousands of program instructions can be extremely tedious and time consuming, and may require a programmer to step through many program instructions that are known to be error-free before a set of instructions to be analyzed are executed.
To address this difficulty, a second operation supported by conventional debuggers is a breakpoint operation, which permits a computer programmer to identify with a “breakpoint” a precise instruction for which it is desired to halt execution of a computer program during execution. As a result, when a computer program is executed by a debugger, the program executes in a normal fashion until a breakpoint is reached, and then stops execution and displays the results of the computer program to the programmer for analysis.
Typically, step operations and breakpoints are used together to simplify the debugging process. Specifically, a common debugging operation is to set a breakpoint at the beginning of a desired set of instructions to be analyzed, and then begin executing the program. Once the breakpoint is reached, the program is halted, and the programmer then steps through the desired set of instructions line by line using the step operation. Consequently, a programmer is able to quickly isolate and analyze a particular set of instructions without having to step through irrelevant portions of a computer program.
In traditional debuggers, breakpoints are generally set on specific statements within functions or methods; program execution is suspended each time the particular statement is called. In some environments, breakpoints may also be specified to be “conditional” so that execution is suspended by a breakpoint only when a particular condition associated with that breakpoint is met (e.g., after the breakpoint has been hit X times).
Statement-based breakpoints have been found to be very useful for debugging program bugs and inefficiencies in traditional procedural languages that are sequential in nature. However, object-oriented languages, which have found increasing acceptability as programming languages, are based on quite a different concept.
Object-oriented programs are typically created using a set of language tools, abstractions and constructs that support a particular form of user-defined type called a “class”. A class functions as a template, and associates a collection of declared data with a set of operations capable of being performed on that data, i.e., methods capable of being called on the data. In an object-oriented program, objects are dynamically created during runtime using the classes as templates. The creation of a new object is often referred to as “instantiation,” whereby an object is an “instance” of a class.
A fundamental feature of object-oriented programming is that classes can be related to one another by inheritance. The properties, behaviors, data and operations of a parent class, i.e., a “base class” or “superclass”, may be inherited without modification by some child class, or “derived class” or “subclass”, or the behavior, properties and operations may be selectively refined under the control of the programmer in the derived class. The latter function is generally referred to as overriding. When defining a derived class, one may start by building on an existing base class that is similar to the one to be created. The derived class inherits the implementation and behavior of the base class, including its methods and data, except as modified by overriding amendments detailed in the derived class definition. Several classes can inherit the behaviors of a common parent, and in some object-oriented languages a derived class may inherit from more than one base class.
Moreover, in some object-oriented programming environments, a developer is permitted to separate a public “interface” for a class from the specifics of its implementation. The public interface represents the externally-accessible attributes of a class, typically specifying what methods need to be provided in any implementations of the class. Actual program code for the defined methods in an interface is not provided within the interface. Instead, a programmer is required to incorporate the actual program code that implements any defined methods within the class that implements the interface. Thus, for example, an application that relies on a database might define an interface to specify the methods that need to be supported to access the database. Individual classes that implement the interface for different types of databases might then be created by the vendors of those databases to provide the actual program code that appropriately accesses their particular database designs.
Also, some environments support the concepts of “abstract” classes, which themselves cannot be instantiated, but which can serve as templates for any subclasses so that any method and data incorporated in the abstract classes will be inherited within the subclasses. Unless overridden in particular subclasses, the methods defined in abstract classes are inherited in the same manner as the methods defined in non-abstract classes.
It is important to note that, regardless of wherever a method identified in a class, abstract class or interface is implemented, the name, or identifier, of that method typically remains the same. As a result, whenever a program makes a call to a particular method, it may not be readily apparent to a programmer exactly what implementation of that method will be executed as a result of the call. Therefore, in order to debug that section of a program, a programmer may be required to hunt through the program to locate all of the possible implementations of a method and manually set breakpoints on those implementations.
However, in a working program, tens or hundreds of classes may be utilized, with those classes interrelated with one another via a complex hierarchy. As a result, manually hunting through a program to locate all possible implementations of a method can be extremely time consuming, and can present the risk that a programmer may miss some of the implementations.
Therefore, a significant need has arisen in the art for an improved manner of debugging object-oriented computer programs. In particular, a significant need has arisen for a manner of assisting a programmer in identifying what implementations of methods are being executed while debugging object-oriented computer programs.